package org.shagin.jfin.ui.budget.tree;

import java.util.List;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;

import org.shagin.jfin.core.Debug;
import org.shagin.jfin.data.model.Account;
import org.shagin.jfin.data.model.CostCenter;
import org.shagin.jfin.data.model.ExpenseRule;
import org.shagin.jfin.data.model.IBudget;
import org.shagin.jfin.data.model.IncomeRule;
import org.shagin.jfin.data.model.OperationCategory;
import org.shagin.jfin.data.model.OperationCategoryLeaf;
import org.shagin.jfin.data.model.OperationCategoryUserObjectCreator;
import org.shagin.jfin.data.model.OperationRule;
import org.shagin.jfin.data.model.TransferRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The Class BudgetTreeModel.
 */
public class BudgetTreeModel extends DefaultTreeModel implements TreeModel {
    private static final long serialVersionUID = 4467793014978936426L;

    private static final Logger log = LoggerFactory
            .getLogger(BudgetTreeModel.class);

    private IBudget budget;
    private RootTreeNode rootNode;
    // TODO Unused
    @SuppressWarnings("unused")
    private OperationCategoryUserObjectCreator<OperationCategoryTreeNode, DefaultMutableTreeNode> ocuoCreator;
    @SuppressWarnings("unused")
    private OperationCategoryUserObjectCreator.IUserObjectHandler<OperationCategoryTreeNode, DefaultMutableTreeNode> ocuoHandler;

    /**
     * The Class RootTreeNode.
     */
    private class RootTreeNode extends DefaultMutableTreeNode {

        private static final long serialVersionUID = 5036104256131171306L;

        /**
         * Instantiates a new root tree node.
         */
        public RootTreeNode() {
            super(budget);
            Debug.traceEnter(log);
            add(new CostCentersTreeNode());
            add(new IncomesTreeNode());
            add(new ExpensesTreeNode());
            add(new TransfersTreeNode());
        }
    }

    /**
     * The Class CostCentersTreeNode.
     */
    private class CostCentersTreeNode extends DefaultMutableTreeNode {

        private static final long serialVersionUID = 5036104256131171306L;

        /**
         * Instantiates a new cost centers tree node.
         */
        public CostCentersTreeNode() {
            super("Coast Centers");
            Debug.traceEnter(log);
            if (budget != null)
                for (CostCenter cc : budget.getCostCenterDomain().getAll()) {
                    add(new CostCenterTreeNode(cc));
                }
        }

    }

    /**
     * The Class IncomesTreeNode.
     */
    @SuppressWarnings("serial")
    public class IncomesTreeNode extends DefaultMutableTreeNode {
        /**
         * Instantiates a new incomes tree node.
         */
        public IncomesTreeNode() {
            // TODO Localize
            super("Incomes");

            createOperationCategoryTreeNodes(this, budget
                    .getOperationRuleDomain().getIncomes(),
                    new ITreeNodeCreator() {

                        @Override
                        public DefaultMutableTreeNode newTreeNode(
                                OperationRule rule) {
                            return new IncomeRuleTreeNode(rule);
                        }

                    });
        }

    }

    /**
     * The Class ExpensesTreeNode.
     */
    @SuppressWarnings("serial")
    public class ExpensesTreeNode extends DefaultMutableTreeNode {

        /**
         * Instantiates a new expenses tree node.
         */
        public ExpensesTreeNode() {
            // TODO Localize
            super("Expenses");
            createOperationCategoryTreeNodes(this, budget
                    .getOperationRuleDomain().getExpenses(),
                    new ITreeNodeCreator() {

                        @Override
                        public DefaultMutableTreeNode newTreeNode(
                                OperationRule rule) {
                            return new ExpenseRuleTreeNode(rule);
                        }

                    });
        }

    }

    /**
     * The Class TransfersTreeNode.
     */
    @SuppressWarnings("serial")
    public class TransfersTreeNode extends DefaultMutableTreeNode {

        /**
         * Instantiates a new transfers tree node.
         */
        public TransfersTreeNode() {
            // TODO Localize
            super("Transfers");
            createOperationCategoryTreeNodes(this, budget
                    .getOperationRuleDomain().getTrasfers(),
                    new ITreeNodeCreator() {

                        @Override
                        public DefaultMutableTreeNode newTreeNode(
                                OperationRule rule) {
                            return new TransferRuleTreeNode(rule);
                        }
                    });
        }

    }

    /**
     * The Class IncomeRuleTreeNode.
     */
    private class IncomeRuleTreeNode extends DefaultMutableTreeNode {

        private static final long serialVersionUID = 6197781398850306829L;

        /**
         * Instantiates a new income rule tree node.
         * 
         * @param rule the rule
         */
        public IncomeRuleTreeNode(OperationRule rule) {
            super(rule);
        }
    }

    /**
     * The Class ExpenseRuleTreeNode.
     */
    private class ExpenseRuleTreeNode extends DefaultMutableTreeNode {

        private static final long serialVersionUID = -7358598843601949167L;

        /**
         * Instantiates a new expense rule tree node.
         * 
         * @param rule the rule
         */
        public ExpenseRuleTreeNode(OperationRule rule) {
            super(rule);
        }
    }

    /**
     * The Class TransferRuleTreeNode.
     */
    private class TransferRuleTreeNode extends DefaultMutableTreeNode {

        private static final long serialVersionUID = -689510603465564425L;

        /**
         * Instantiates a new transfer rule tree node.
         * 
         * @param rule the rule
         */
        public TransferRuleTreeNode(OperationRule rule) {
            super(rule);
        }
    }

    /**
     * The Class OperationCategoryTreeNode.
     */
    private class OperationCategoryTreeNode extends DefaultMutableTreeNode {

        private static final long serialVersionUID = 2739424919832760821L;

        /**
         * Instantiates a new operation category tree node.
         * 
         * @param cat the cat
         */
        public OperationCategoryTreeNode(OperationCategory cat) {
            super(cat);
        }
    }

    /**
     * The Class CostCenterTreeNode.
     */
    private class CostCenterTreeNode extends DefaultMutableTreeNode {

        private static final long serialVersionUID = 5036104256131171306L;

        /**
         * Instantiates a new cost center tree node.
         * 
         * @param cc the cc
         */
        public CostCenterTreeNode(final CostCenter cc) {
            super(cc);
            if (cc == null)
                throw new IllegalArgumentException();
            Debug.traceEnter(log);

            for (Account account : cc.getAccounts()) {
                add(new AccountTreeNode(account));
            }
        }

    }

    /**
     * The Class AccountTreeNode.
     */
    private class AccountTreeNode extends DefaultMutableTreeNode {

        private static final long serialVersionUID = -375990094498548186L;

        /**
         * Instantiates a new account tree node.
         * 
         * @param account the account
         */
        public AccountTreeNode(final Account account) {
            super(account);
            if (account == null)
                throw new IllegalArgumentException();
            Debug.traceEnter(log);
        }

    }

    /**
     * Instantiates a new budget tree model.
     * 
     * @param budget the budget
     */
    public BudgetTreeModel(IBudget budget) {
        super(null);
        Debug.traceEnter(log);

        ocuoCreator = new OperationCategoryUserObjectCreator<OperationCategoryTreeNode, DefaultMutableTreeNode>();
        ocuoHandler = new OperationCategoryUserObjectCreator.IUserObjectHandler<OperationCategoryTreeNode, DefaultMutableTreeNode>() {

            @Override
            public DefaultMutableTreeNode newLeafUserObject(OperationRule rule) {
                DefaultMutableTreeNode result = null;
                if (rule instanceof IncomeRule)
                    result = new IncomeRuleTreeNode(rule);
                else if (rule instanceof ExpenseRule)
                    result = new ExpenseRuleTreeNode(rule);
                else if (rule instanceof TransferRule)
                    result = new TransferRuleTreeNode(rule);
                else
                    throw new IllegalArgumentException();
                return result;
            }

            @Override
            public void addLeafUserObject(OperationCategoryTreeNode parent,
                    DefaultMutableTreeNode obj) {
                if (parent != null)
                    parent.add(obj);
            }

            @Override
            public OperationCategoryTreeNode newCategoryUserObject(
                    OperationCategory cat) {
                return new OperationCategoryTreeNode(cat);
            }

            @Override
            public void addCategoryUserObject(OperationCategoryTreeNode parent,
                    OperationCategoryTreeNode obj) {
                if (parent != null)
                    parent.add(obj);
            }

        };

        if (budget == null)
            throw new IllegalArgumentException();
        this.budget = budget;
        rootNode = new RootTreeNode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getRoot() {
        return rootNode;
    }

    /**
     * The Interface ITreeNodeCreator.
     */
    private interface ITreeNodeCreator {

        /**
         * New tree node.
         * 
         * @param rule the rule
         * @return the default mutable tree node
         */
        DefaultMutableTreeNode newTreeNode(OperationRule rule);
    }

    /**
     * Creates the operation category tree nodes.
     * 
     * @param parent the parent
     * @param rules the rules
     * @param creator the creator
     */
    private void createOperationCategoryTreeNodes(
            DefaultMutableTreeNode parent, List<OperationRule> rules,
            ITreeNodeCreator creator) {

        for (OperationCategory root : OperationRule.getRoots(rules))
            parent.add(addOperationCategoryTreeNode(null, root, creator));

    }

    /**
     * Adds the operation category tree node.
     * 
     * @param parent the parent
     * @param cat the cat
     * @param creator the creator
     * @return the operation category tree node
     */
    private OperationCategoryTreeNode addOperationCategoryTreeNode(
            OperationCategoryTreeNode parent, OperationCategory cat,
            ITreeNodeCreator creator) {
        OperationCategoryTreeNode treeNode = new OperationCategoryTreeNode(cat);

        if (null != parent) {
            parent.add(treeNode);
        }

        if (cat instanceof OperationCategoryLeaf) {
            OperationCategoryLeaf c = (OperationCategoryLeaf) cat;
            for (OperationRule rule : c.getRules()) {
                treeNode.add(creator.newTreeNode(rule));
            }
        } else
            for (OperationCategory child : cat.getChilds())
                addOperationCategoryTreeNode(treeNode, child, creator);

        return treeNode;
    }
}
